package com.cgnpc.scp.common.utils;

import com.cgnpc.scp.common.config.CustomSpringConfig;
import com.cgnpc.scp.common.exception.ScpException;
import com.cgnpc.scp.common.service.AppConstantService;
import org.apache.tika.config.TikaConfig;
import org.apache.tika.detect.Detector;
import org.apache.tika.io.TikaInputStream;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.mime.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpInputMessage;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;

/**
 * 文件工具类
 */
public class FileUtil {
    private static final Logger LOGGER = LoggerFactory.getLogger(FileUtil.class);

    /**
     * <p>正则表达式，安全文件</p>
     */
    public static final String STR_REGEX_SAFE_FILENAME = "^[A-Za-z0-9\\-\\_]{1,128}(\\.[A-Za-z0-9]+)?$";

    /**
     * <P>正则表达式，安全的文件后缀</P>
     */
    public static final String STR_REGEX_SAFE_FILENAME_SUFFIX = "^\\.[A-Za-z0-9]+$";

    /**
     * <p>正则表达式，安全的下载文件</p>
     * <p>安全路径只能使用有限的字符构成，例如..返回上一层将不被允许。</p>
     */
    public static final String STR_REGEX_SAFE_DOWNLOAD_FILE_LINUX = "^(/[A-Za-z0-9_-]+)+$";

    /**
     * <p>正则表达式，安全的下载文件</p>
     * <p>安全路径只能使用有限的字符构成，例如..返回上一层将不被允许。</p>
     * <p>
     * windows的目录，例如："c:\\_my\\_workspaces\\___temp\\___---001\\version_ini\\..\\..\\..\\.\\.\\.\\___temp"，
     * 最后将指向C:\_my\_workspaces\___temp目录。
     * 安全路径只能使用有限的字符构成。
     * </p>
     */
    public static final String STR_REGEX_SAFE_DOWNLOAD_FILE_WINDOWS = "^[A-Za-z]:([\\\\/][A-Za-z0-9_-]+)+$";

    /**
     * <p>正则表达式，安全文件</p>
     */
    public static final Pattern PATTERN_SAFE_FILENAME = Pattern.compile(STR_REGEX_SAFE_FILENAME);

    /**
     * <P>正则表达式，安全的文件后缀</P>
     */
    public static final Pattern PATTERN_SAFE_FILENAME_SUFFIX = Pattern.compile(STR_REGEX_SAFE_FILENAME_SUFFIX);

    /**
     * <p>正则表达式，安全的下载文件</p>
     *
     * @see #STR_REGEX_SAFE_DOWNLOAD_FILE_LINUX
     */
    public static final Pattern PATTERN_SAVE_DOWNLOAD_FILE_LINUX = Pattern.compile(STR_REGEX_SAFE_DOWNLOAD_FILE_LINUX);

    /**
     * <p>正则表达式，安全的下载文件</p>
     *
     * @see #STR_REGEX_SAFE_DOWNLOAD_FILE_WINDOWS
     */
    public static final Pattern PATTERN_SAVE_DOWNLOAD_FILE_WINDOWS = Pattern.compile(STR_REGEX_SAFE_DOWNLOAD_FILE_WINDOWS);

    /**
     * 安全文件的类型
     */
    private static final Map<String, List<String>> SAFE_FILE_WITH_MIME_TYPE_MAP = new HashMap<>();

    /**
     * 针对上传文件的安全文件类型，小写类型
     */
    private static final List<String> SAFE_FILE_NAME_LOWER_SUFFIX_UPLOAD_FILE = Arrays.asList(
            ".doc,.docx,.mp3,.pdf,.ppt,.pptx,.txt,.wps,.xls,.xlsx,.rar,.zip,.jpg,.jpeg".toLowerCase().split(",")
    );



    static {
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".xls", Arrays.asList("application/vnd.ms-excel"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".xlsx", Arrays.asList("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".ppt", Arrays.asList("application/vnd.ms-powerpoint"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".pptx", Arrays.asList("application/vnd.openxmlformats-officedocument.presentationml.presentation"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".doc", Arrays.asList("application/msword"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".docx", Arrays.asList("application/vnd.openxmlformats-officedocument.wordprocessingml.document"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".rar", Arrays.asList("application/x-rar-compressed; version=4", "application/x-rar-compressed; version=5"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".7z", Arrays.asList("application/x-7z-compressed"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".txt", Arrays.asList("text/plain"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".zip", Arrays.asList("application/zip"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".mp3", Arrays.asList("audio/mpeg"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".jpg", Arrays.asList("image/jpeg"));
        SAFE_FILE_WITH_MIME_TYPE_MAP.put(".jpeg", Arrays.asList("image/jpeg"));
    }

    /**
     * 是否安全文件名称
     *
     * @param fileName 文件名称
     * @return 值true安全文件名称；false非安全文件名称；
     */
    public static boolean isSafeFileName(String fileName) {
        return fileName != null && PATTERN_SAFE_FILENAME.matcher(fileName).matches();
    }

    /**
     * 是否安全文件名称后缀
     *
     * @param fileName 文件名称
     * @return 值true安全文件名称；false非安全文件名称；
     */
    public static boolean isSafeFileNameSuffix(String fileName) {
        return fileName != null && PATTERN_SAFE_FILENAME_SUFFIX.matcher(fileName).matches();
    }

    /**
     * 将文件名称格式化为安全的文件名称
     *
     * @param srcFileName 原始的文件名称
     * @return 安全的文件名称
     */
    public static String formatSafeFileName(String srcFileName) {
        if (StringUtil.isNullOrSpace(srcFileName)) {
            return System.currentTimeMillis() + UUID.randomUUID().toString();
        }

        if (isSafeFileName(srcFileName)) {
            return srcFileName;
        }

        if (isSafeFileNameSuffix(srcFileName)) {
            return System.currentTimeMillis() + UUID.randomUUID().toString()
                    + srcFileName.substring(srcFileName.lastIndexOf("."));
        }

        return System.currentTimeMillis() + UUID.randomUUID().toString();
    }

    /**
     * 创建安全文件到内部文件夹
     *
     * @param appConstantService appConstantService
     * @param fileName           文件名称
     * @return 安全文件
     */
    public static File createSafeFileToInsideLocalDir(AppConstantService appConstantService, String fileName) {
        return new File(appConstantService.getTempCreateFileDir(), formatSafeFileName(fileName));
    }

    /**
     * 校验上传的文件是否安全的文件
     *
     * @param reqFileList 上传的文件列表
     * @return 值true安全文件，false非安全文件
     */
    public static boolean isSafeFileWithUploadFile(List<MultipartFile> reqFileList) {
        if (ListUtil.getListSizeWith0(reqFileList) <= 0) {
            return false;
        }

        boolean reqFileEmpty = true;
        long reqFileSize = 0;
        String reqFileOriginalFilename = null;
        String reqFileOriginalFilenameLower = null;
        String reqFileName = null;
        String reqFileContentType = null;

        List<Boolean> reqFileEmptyList = new ArrayList<>();

        for (MultipartFile reqFile : reqFileList) {
            try {
                reqFileEmpty = reqFile.isEmpty();
                reqFileSize = reqFile.getSize();
                reqFileOriginalFilename = reqFile.getOriginalFilename();
                reqFileName = reqFile.getName();
                reqFileContentType = reqFile.getContentType();

                LOGGER.info("校验文件；"
                                + "reqFileEmpty:{};"
                                + "reqFileOriginalFilename:{};reqFileName:{};,reqFileSize:{};"
                                + "reqFileContentType:{};"
                        , reqFileEmpty
                        , reqFileOriginalFilename, reqFileName, reqFileSize
                        , reqFileContentType
                );

                reqFileEmptyList.add(reqFileEmpty);

                if (reqFileEmpty) {
                    continue;
                }

                reqFileOriginalFilenameLower = reqFileOriginalFilename.toLowerCase(Locale.ENGLISH);

                if (ListUtil.getListSizeWith0(SAFE_FILE_NAME_LOWER_SUFFIX_UPLOAD_FILE) > 0) {
                    for (String safeFileNameSuffixWithUploadFile : SAFE_FILE_NAME_LOWER_SUFFIX_UPLOAD_FILE) {
                        if (reqFileOriginalFilenameLower.endsWith(safeFileNameSuffixWithUploadFile)) {
                            return true;
                        }
                    }
                }
            } catch (Exception e) {
                LOGGER.error("error", e);
                return false;
            } finally {

            }
        }

        if (reqFileEmptyList.contains(false)) {
            return false;
        } else {
            return true;
        }
    }

    private static boolean isSafeFileWithUploadFile(File file) {
        if (file == null) {
            return false;
        }

        TikaInputStream tikaInputStream = null;

        try {
            TikaConfig tikaConfig = new TikaConfig();
            Detector detector = tikaConfig.getDetector();
            Metadata metadata = new Metadata();
            metadata.set(Metadata.RESOURCE_NAME_KEY, file.toString());
            tikaInputStream = TikaInputStream.get(file.toPath());
            MediaType mediaType = detector.detect(tikaInputStream, metadata);

            String mimetype = mediaType == null ? null : mediaType.toString();

            LOGGER.info("File:   " + file
                            + "\t fileSize: " + file.length()
                            + "\t     mediaType: " + mediaType
                            + "\tmimetype: " + mimetype
                    // + "\t\t" + JacksonUtil.writeValueAsString(mediaType)
            );
        } catch (Exception e) {
            LOGGER.error("error", e);
        } finally {
            IOUtil.close(tikaInputStream);
        }

        return false;
    }

    /**
     * 创建多层目录
     */
    public static boolean mkdirs(File file) {
        if (file != null && !file.exists()) {
            boolean mkdirsResult = file.mkdirs();
            LOGGER.info("file:{};mkdirsResult:{};", file, mkdirsResult);
            return mkdirsResult;
        }

        return false;
    }

    /**
     * 删除文件
     */
    public static boolean delete(File file) {
        if (file != null && file.exists()) {
            boolean fileDeleteResult = file.delete();
            LOGGER.info("file:{};fileDeleteResult:{};", file, fileDeleteResult);
            return fileDeleteResult;
        }

        return false;
    }

//    public static void main(String[] args) throws Exception {
//        main_safeDownloadFile();
//    }
//
//    public static void main_safeDownloadFile() throws Exception {
//        String reqDownloadFileString = "";
//
//        // "c:\\_my\\_workspaces\\___temp\\___---001\\version_ini\\..\\..\\..\\.\\.\\.\\___temp"
//        // \_my\_workspaces\___temp
//        // /_my/_workspaces/___temp
//
//        // 最后将指向：C:\_my\_workspaces\___temp 目录，而且真实存在，指向文件也如此
//        reqDownloadFileString = "c:\\_my\\_workspaces\\___temp\\___---001\\version_ini\\..\\..\\..\\.\\.\\.\\___temp";
//        reqDownloadFileString = "\\_my\\_workspaces\\___temp\\___---001\\version_ini\\..\\..\\..\\.\\.\\.\\___temp";
//        reqDownloadFileString = "/_my/_workspaces/___temp\\";
//
//        File file_001 = new File(reqDownloadFileString);
//        String file_001CanonicalPath = file_001.getCanonicalPath();
//
//        boolean file_001_regex_linux = PATTERN_SAVE_DOWNLOAD_FILE_LINUX.matcher(file_001CanonicalPath).matches();
//        boolean file_001_regex_windows = PATTERN_SAVE_DOWNLOAD_FILE_WINDOWS.matcher(file_001CanonicalPath).matches();
//
//        System.out.println(""
//                + "\r\n" + "File.pathSeparator: " + File.pathSeparator
//                + "\r\n" + "File.separator: " + File.separator
//                + "\r\n" + "reqDownloadFileString: " + reqDownloadFileString
//                + "\r\n" + "file_001.getCanonicalPath: " + file_001CanonicalPath
//                + "\r\n" + "file_001.exists: " + file_001.exists()
//                + "\r\n" + "file_001_regex_linux: " + file_001_regex_linux
//                + "\r\n" + "file_001_regex_windows: " + file_001_regex_windows
//        );
//    }
        public static String fileNameValidate(String str) {

            String strInjectListStr = "../|./|/..| |<|>|:|?";
            if (null != strInjectListStr && !"".equals(strInjectListStr)) {
                str = str.toLowerCase();
                String[] badStrs = strInjectListStr.split("\\|");
                for (int i = 0; i < badStrs.length; i++) {
                    if (str.indexOf(badStrs[i]) >= 0) {
                        str = str.replace(badStrs[i], "");
                    }
                }
            }
            return str;
        }
    /**
     * <p>校验下载文件是否为安全的路径</p>
     * <p>
     * 1.指定路径下的文件才是安全的。
     * 2.路径格式校验，只能为大小写字母，数字，短横杠-，斜杆（除号）/。
     * 3.不能包含..返回上一层的特殊含义字符。
     * </p>
     *
     * @param downloadFile 下载文件
     * @return
     * @see com.cgnpc.scp.common.bean.CustomDownloadFileResourceHttpMessageConverter#readInternal(Class, HttpInputMessage)
     * @see CustomSpringConfig#getInsideTempDir()
     */
    public static File formatSafeFileWithDownloadFile(File downloadFile) throws Exception {

        if (downloadFile == null) {
            return null;
        }

        final String logInfo = "[formatSafeFileWithDownloadFile_UUID_" + UUID.randomUUID().toString() + "]";

        try {
            // 下载文件的路径
            String downloadFileCanonicalPath = downloadFile.getCanonicalPath();
            downloadFileCanonicalPath =fileNameValidate(downloadFileCanonicalPath);
            File downloadFileNew = new File(downloadFileCanonicalPath);
            org.aspectj.util.FileUtil.getBestFile(new String[]{});

            LOGGER.info("{}downloadFile:{};downloadFileCanonicalPath:{};"
                            + "downloadFileNew:{};"
                    , logInfo
                    , downloadFile, downloadFileCanonicalPath
                    , downloadFileNew
            );

            // 如果包含了..字符，则校验失败
            if (downloadFileCanonicalPath.indexOf("..") != -1) {
                LOGGER.error("{}error;downloadFileCanonicalPath has string[..];", logInfo);
                return null;
            }

            // 使用正则表达式校验路径，如果非linux，windows路径方式，则校验失败
            if (!(PATTERN_SAVE_DOWNLOAD_FILE_LINUX.matcher(downloadFileCanonicalPath).matches()
                    || PATTERN_SAVE_DOWNLOAD_FILE_WINDOWS.matcher(downloadFileCanonicalPath).matches()
            )) {
                return null;
            }

            // 指定路径下的文件才能下载
            File fileInsideTempDir = CustomSpringConfig.getInsideTempDir();
            String fileInsideTempDirCanonicalPath = fileInsideTempDir.getCanonicalPath();
            // 一般来说File.getCanonicalPath()会移除\，/
            fileInsideTempDirCanonicalPath = fileInsideTempDirCanonicalPath + File.separator;
            LOGGER.info("{}fileInsideTempDir:{};fileInsideTempDirCanonicalPath:{};"
                    , logInfo
                    , fileInsideTempDir, fileInsideTempDirCanonicalPath
            );

            // 下载文件必须为指定目录下的文件才能下载
            if (downloadFileCanonicalPath.indexOf(fileInsideTempDirCanonicalPath) == 0) {
                // 校验通过返回新的文件对象
                return downloadFileNew;
            }
        } catch (Exception e) {
            LOGGER.error("isSafeFileWithDownloadFile;error;", e);
            throw e;
        }

        return null;
    }
    public static String validyStr(String str) {
        if (Pattern.matches("[0-9A-Za-z]+", str)) {
            return str;
        }else {
            throw new ScpException("文件名不合法");
        }
    }


    public static String validyStr1(String haha) {
        //if (Pattern.matches("[0-9A-Za-z]+", str)) {
        try {
            return "attachment; filename"+new String(haha.getBytes("utf-8"), "ISO8859-1");
        }catch (Exception e){
            e.printStackTrace();
        }
        return "";
        // }else {
        //     throw new ScpException("文件名不合法");
        // }
    }
}
